6 changed files with 329 additions and 1 deletions
			
			
		- 
					1.gitignore
 - 
					5client/jumpcli.go
 - 
					297client/jumpcli.py
 - 
					1client/requirements.txt
 - 
					24examples/cmd-menu.py
 - 
					2server/manager_user.sh
 
@ -1,3 +1,4 @@ | 
				
			|||
jumpserver | 
				
			|||
.idea | 
				
			|||
.idea/ | 
				
			|||
examples/ | 
				
			|||
@ -0,0 +1,297 @@ | 
				
			|||
#!/usr/bin/env python3 | 
				
			|||
# -*- coding: UTF-8 -*- | 
				
			|||
 | 
				
			|||
import paramiko | 
				
			|||
import os | 
				
			|||
import sys | 
				
			|||
import curses | 
				
			|||
import termios | 
				
			|||
import tty | 
				
			|||
import socket | 
				
			|||
import signal | 
				
			|||
import getpass | 
				
			|||
import platform | 
				
			|||
import sqlite3 | 
				
			|||
 | 
				
			|||
KEYS_ENTER = (curses.KEY_ENTER, ord('\n'), ord('\r')) | 
				
			|||
KEYS_UP = (curses.KEY_UP, ord('k')) | 
				
			|||
KEYS_DOWN = (curses.KEY_DOWN, ord('j')) | 
				
			|||
KEYS_SELECT = (curses.KEY_RIGHT, ord(' ')) | 
				
			|||
 | 
				
			|||
# 数据库文件 | 
				
			|||
gSqlite3File = "/usr/local/jumpserver/jumpserver.db" | 
				
			|||
 | 
				
			|||
# ssh_private_path ssh私钥路径 | 
				
			|||
system_type = platform.system() | 
				
			|||
if system_type == "Darwin": | 
				
			|||
    ssh_private_path = "/Users/%s/.ssh/id_rsa" | 
				
			|||
elif system_type == "Linux": | 
				
			|||
    ssh_private_path = "/home/%s/.ssh/id_rsa" | 
				
			|||
else: | 
				
			|||
    exit(1) | 
				
			|||
 | 
				
			|||
 | 
				
			|||
class Picker(object): | 
				
			|||
    def __init__(self, options, title=None, indicator='*', default_index=0, multiselect=False, multi_select=False, | 
				
			|||
                 min_selection_count=0, options_map_func=None): | 
				
			|||
 | 
				
			|||
        if len(options) == 0: | 
				
			|||
            raise ValueError('options should not be an empty list') | 
				
			|||
 | 
				
			|||
        self.options = options | 
				
			|||
        self.title = title | 
				
			|||
        self.indicator = indicator | 
				
			|||
        self.multiselect = multiselect or multi_select | 
				
			|||
        self.min_selection_count = min_selection_count | 
				
			|||
        self.options_map_func = options_map_func | 
				
			|||
        self.all_selected = [] | 
				
			|||
 | 
				
			|||
        if default_index >= len(options): | 
				
			|||
            raise ValueError('default_index should be less than the length of options') | 
				
			|||
 | 
				
			|||
        if multiselect and min_selection_count > len(options): | 
				
			|||
            raise ValueError( | 
				
			|||
                'min_selection_count is bigger than the available options, you will not be able to make any selection') | 
				
			|||
 | 
				
			|||
        if options_map_func is not None and not callable(options_map_func): | 
				
			|||
            raise ValueError('options_map_func must be a callable function') | 
				
			|||
 | 
				
			|||
        self.index = default_index | 
				
			|||
        self.custom_handlers = {} | 
				
			|||
 | 
				
			|||
    def register_custom_handler(self, key, func): | 
				
			|||
        self.custom_handlers[key] = func | 
				
			|||
 | 
				
			|||
    def move_up(self): | 
				
			|||
        self.index -= 1 | 
				
			|||
        if self.index < 0: | 
				
			|||
            self.index = len(self.options) - 1 | 
				
			|||
 | 
				
			|||
    def move_down(self): | 
				
			|||
        self.index += 1 | 
				
			|||
        if self.index >= len(self.options): | 
				
			|||
            self.index = 0 | 
				
			|||
 | 
				
			|||
    def mark_index(self): | 
				
			|||
        if self.multiselect: | 
				
			|||
            if self.index in self.all_selected: | 
				
			|||
                self.all_selected.remove(self.index) | 
				
			|||
            else: | 
				
			|||
                self.all_selected.append(self.index) | 
				
			|||
 | 
				
			|||
    def get_selected(self): | 
				
			|||
        """return the current selected option as a tuple: (option, index) | 
				
			|||
           or as a list of tuples (in case multiselect==True) | 
				
			|||
        """ | 
				
			|||
        if self.multiselect: | 
				
			|||
            return_tuples = [] | 
				
			|||
            for selected in self.all_selected: | 
				
			|||
                return_tuples.append((self.options[selected], selected)) | 
				
			|||
            return return_tuples | 
				
			|||
        else: | 
				
			|||
            return self.options[self.index], self.index | 
				
			|||
 | 
				
			|||
    def get_title_lines(self): | 
				
			|||
        if self.title: | 
				
			|||
            return self.title.split('\n') + [''] | 
				
			|||
        return [] | 
				
			|||
 | 
				
			|||
    def get_option_lines(self): | 
				
			|||
        lines = [] | 
				
			|||
        for index, option in enumerate(self.options): | 
				
			|||
            # pass the option through the options map of one was passed in | 
				
			|||
            if self.options_map_func: | 
				
			|||
                option = self.options_map_func(option) | 
				
			|||
 | 
				
			|||
            if index == self.index: | 
				
			|||
                prefix = self.indicator | 
				
			|||
            else: | 
				
			|||
                prefix = len(self.indicator) * ' ' | 
				
			|||
 | 
				
			|||
            if self.multiselect and index in self.all_selected: | 
				
			|||
                format = curses.color_pair(1) | 
				
			|||
                line = ('{0} {1}'.format(prefix, option), format) | 
				
			|||
            else: | 
				
			|||
                line = '{0} {1}'.format(prefix, option) | 
				
			|||
            lines.append(line) | 
				
			|||
 | 
				
			|||
        return lines | 
				
			|||
 | 
				
			|||
    def get_lines(self): | 
				
			|||
        title_lines = self.get_title_lines() | 
				
			|||
        option_lines = self.get_option_lines() | 
				
			|||
        lines = title_lines + option_lines | 
				
			|||
        current_line = self.index + len(title_lines) + 1 | 
				
			|||
        return lines, current_line | 
				
			|||
 | 
				
			|||
    def draw(self): | 
				
			|||
        """draw the curses ui on the screen, handle scroll if needed""" | 
				
			|||
        self.screen.clear() | 
				
			|||
 | 
				
			|||
        x, y = 1, 1  # start point | 
				
			|||
        max_y, max_x = self.screen.getmaxyx() | 
				
			|||
        max_rows = max_y - y  # the max rows we can draw | 
				
			|||
 | 
				
			|||
        lines, current_line = self.get_lines() | 
				
			|||
 | 
				
			|||
        # calculate how many lines we should scroll, relative to the top | 
				
			|||
        scroll_top = getattr(self, 'scroll_top', 0) | 
				
			|||
        if current_line <= scroll_top: | 
				
			|||
            scroll_top = 0 | 
				
			|||
        elif current_line - scroll_top > max_rows: | 
				
			|||
            scroll_top = current_line - max_rows | 
				
			|||
        self.scroll_top = scroll_top | 
				
			|||
 | 
				
			|||
        lines_to_draw = lines[scroll_top:scroll_top + max_rows] | 
				
			|||
 | 
				
			|||
        for line in lines_to_draw: | 
				
			|||
            if type(line) is tuple: | 
				
			|||
                self.screen.addnstr(y, x, line[0], max_x - 2, line[1]) | 
				
			|||
            else: | 
				
			|||
                self.screen.addnstr(y, x, line, max_x - 2) | 
				
			|||
            y += 1 | 
				
			|||
 | 
				
			|||
        self.screen.refresh() | 
				
			|||
 | 
				
			|||
    def run_loop(self): | 
				
			|||
        while True: | 
				
			|||
            self.draw() | 
				
			|||
            c = self.screen.getch() | 
				
			|||
            if c in KEYS_UP: | 
				
			|||
                self.move_up() | 
				
			|||
            elif c in KEYS_DOWN: | 
				
			|||
                self.move_down() | 
				
			|||
            elif c in KEYS_ENTER: | 
				
			|||
                if self.multiselect and len(self.all_selected) < self.min_selection_count: | 
				
			|||
                    continue | 
				
			|||
                return self.get_selected() | 
				
			|||
            elif c in KEYS_SELECT and self.multiselect: | 
				
			|||
                self.mark_index() | 
				
			|||
            elif c in self.custom_handlers: | 
				
			|||
                ret = self.custom_handlers[c](self) | 
				
			|||
                if ret: | 
				
			|||
                    return ret | 
				
			|||
            elif c == ord('q'): | 
				
			|||
                exit(0) | 
				
			|||
 | 
				
			|||
    def config_curses(self): | 
				
			|||
        try: | 
				
			|||
            # use the default colors of the terminal | 
				
			|||
            curses.use_default_colors() | 
				
			|||
            # hide the cursor | 
				
			|||
            curses.curs_set(0) | 
				
			|||
            # add some color for multi_select | 
				
			|||
            # @todo make colors configurable | 
				
			|||
            curses.init_pair(1, curses.COLOR_GREEN, curses.COLOR_WHITE) | 
				
			|||
        except: | 
				
			|||
            # Curses failed to initialize color support, eg. when TERM=vt100 | 
				
			|||
            curses.initscr() | 
				
			|||
 | 
				
			|||
    def _start(self, screen): | 
				
			|||
        self.screen = screen | 
				
			|||
        self.config_curses() | 
				
			|||
        return self.run_loop() | 
				
			|||
 | 
				
			|||
    def start(self): | 
				
			|||
        return curses.wrapper(self._start) | 
				
			|||
 | 
				
			|||
 | 
				
			|||
def updateWindowHandler(signum): | 
				
			|||
    if signum == signal.SIGWINCH: | 
				
			|||
        width, height = os.get_terminal_size() | 
				
			|||
        os.terminal_size((width, height)) | 
				
			|||
 | 
				
			|||
 | 
				
			|||
def posix_shell(chan): | 
				
			|||
    import select | 
				
			|||
    oldtty = termios.tcgetattr(sys.stdin) | 
				
			|||
    signal.signal(signal.SIGWINCH, updateWindowHandler) | 
				
			|||
 | 
				
			|||
    try: | 
				
			|||
        tty.setraw(sys.stdin.fileno(), termios.TCIOFLUSH) | 
				
			|||
        # tty.setcbreak(sys.stdin.fileno()) | 
				
			|||
        chan.settimeout(0.0) | 
				
			|||
        while True: | 
				
			|||
            r, w, e = select.select([chan, sys.stdin], [], []) | 
				
			|||
            if chan in r: | 
				
			|||
                try: | 
				
			|||
                    data = str(chan.recv(1024), encoding='utf-8') | 
				
			|||
                    if len(data) == 0: | 
				
			|||
                        break | 
				
			|||
                    sys.stdout.write(data) | 
				
			|||
                    sys.stdout.flush() | 
				
			|||
                except socket.timeout: | 
				
			|||
                    pass | 
				
			|||
            if sys.stdin in r: | 
				
			|||
                ch = sys.stdin.read(1) | 
				
			|||
                if len(ch) == 0: | 
				
			|||
                    break | 
				
			|||
                chan.send(ch) | 
				
			|||
    finally: | 
				
			|||
        termios.tcsetattr(sys.stdin, termios.TCSADRAIN, oldtty) | 
				
			|||
 | 
				
			|||
 | 
				
			|||
def NewTerminal(host, port, user): | 
				
			|||
    # 建立ssh连接 | 
				
			|||
    ssh = paramiko.SSHClient() | 
				
			|||
    ssh.load_system_host_keys() | 
				
			|||
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) | 
				
			|||
 | 
				
			|||
    ssh.connect( | 
				
			|||
        host, | 
				
			|||
        port=port, | 
				
			|||
        username=user, | 
				
			|||
        key_filename=ssh_private_path % user, | 
				
			|||
        compress=False) | 
				
			|||
 | 
				
			|||
    # 建立交互式shell连接 | 
				
			|||
    width, height = os.get_terminal_size() | 
				
			|||
    channel = ssh.invoke_shell("xterm-256color", width, height) | 
				
			|||
 | 
				
			|||
    # 建立交互式管道 | 
				
			|||
    posix_shell(channel) | 
				
			|||
 | 
				
			|||
    # 关闭连接 | 
				
			|||
    channel.close() | 
				
			|||
    ssh.close() | 
				
			|||
 | 
				
			|||
 | 
				
			|||
# 初始化表 | 
				
			|||
def connect_db(): | 
				
			|||
    # 连接数据库 | 
				
			|||
    conn = sqlite3.connect(gSqlite3File) | 
				
			|||
    if conn == None: | 
				
			|||
        print("sqlite3.connect " + gSqlite3File + "failed!") | 
				
			|||
        exit(1) | 
				
			|||
    return conn | 
				
			|||
 | 
				
			|||
def get_hosts(user): | 
				
			|||
    db = connect_db() | 
				
			|||
    hosts = db.execute("select name,ip,port from hosts where isdelete=0 and name in (select hostname from hostuser where username='%s') order by id" % user).fetchall() | 
				
			|||
    resp = [] | 
				
			|||
    for host in hosts: | 
				
			|||
        resp.append("%s:%s:%s" % (host[0], host[1], host[2])) | 
				
			|||
 | 
				
			|||
    if len(resp) == 0: | 
				
			|||
        print(user + " no valid hosts") | 
				
			|||
        exit(1) | 
				
			|||
    return resp | 
				
			|||
 | 
				
			|||
def main(): | 
				
			|||
    user = getpass.getuser() | 
				
			|||
    print("current user: " + user) | 
				
			|||
 | 
				
			|||
    title = "ssh hosts select:" | 
				
			|||
    menu = get_hosts(user) | 
				
			|||
 | 
				
			|||
    while True: | 
				
			|||
        option, index = Picker(menu, title).start() | 
				
			|||
        arr = option.split(":") | 
				
			|||
        if len(arr) == 3: | 
				
			|||
            host = arr[1] | 
				
			|||
            port = int(arr[2]) | 
				
			|||
            NewTerminal(host, port, user) | 
				
			|||
 | 
				
			|||
 | 
				
			|||
if __name__ == '__main__': | 
				
			|||
    main() | 
				
			|||
@ -0,0 +1 @@ | 
				
			|||
paramiko==2.7.1 | 
				
			|||
@ -0,0 +1,24 @@ | 
				
			|||
# -*- coding: UTF-8 -*- | 
				
			|||
 | 
				
			|||
from pick import pick | 
				
			|||
import sshserver | 
				
			|||
 | 
				
			|||
title = 'Please choose your favorite programming language: ' | 
				
			|||
# options = ['quit', 'Java', 'JavaScript', 'Python', 'PHP', 'C++', 'Erlang', 'Haskell'] | 
				
			|||
options = ['ckqas:192.168.1.44:22', 'ckqas129:192.168.1.129:22'] | 
				
			|||
# option, index = pick(options, title) | 
				
			|||
# print(option, index) | 
				
			|||
 | 
				
			|||
 | 
				
			|||
if __name__ == '__main__': | 
				
			|||
    while True: | 
				
			|||
        option, index = pick(options, title) | 
				
			|||
        if option == "quit": | 
				
			|||
            exit(0) | 
				
			|||
 | 
				
			|||
        arr = option.split(":") | 
				
			|||
        if len(arr) == 3: | 
				
			|||
            host = arr[1] | 
				
			|||
            port = int(arr[2]) | 
				
			|||
            sshserver.NewTerminal(host, port, "daniel") | 
				
			|||
 | 
				
			|||
						Write
						Preview
					
					
					Loading…
					
					Cancel
						Save
					
		Reference in new issue